#ifndef BASE_CTSOCKET_H_
#define BASE_CTSOCKET_H_
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <string>
#include <errno.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <zlib.h>
#include <vector>

#include "ctType.h"
#include "ct_noncopyable.h"
#include "ctRWLock.h"
#include "ct_mutex.h"
#include "ctTime.h"
#include "ct_encrypt.h"
#include "Zebra.h"
#include "ct_byte_buffer.h"

class ctSocket : private ctNoncopyable
{
        public:
                ctSocket(const int sock,const struct sockaddr_in *addr = NULL,const bool compress = false, const bool lostpkg= false);
                ~ctSocket();

                static const int T_RD_MSEC		=  2100;		//rd time out msec
                static const int T_WR_MSEC		=  2100;		//wr time out msec

                static const DWORD PH_LEN		=  sizeof(DWORD);	//head len
                static const DWORD PACKET_ZIP_MIN	=  32;			//zip min size

                static const DWORD PACKET_ZIP		=  0x40000000;		//zip flag
                static const DWORD INCOMPLETE_READ	=  0x00000001;		//last read incomplete flag
                static const DWORD INCOMPLETE_WRITE	=  0x00000002;		//last write incomplete flag

                static const DWORD PACKET_MASK;	                		//max pkg len mask
                static const DWORD MAX_DATABUFFERSIZE;	                        //data pkg MAX size(include 4 Bytes HEAD)
                static const DWORD MAX_DATASIZE;		                //data pkg MAX size
                static const DWORD MAX_USERDATASIZE;	                        //user data pkg MAX size
                static const DWORD MIN_FREEDATASIZE;

                static const char *getIPByIfName(const char *ifName);


                int recvToCmd(void *pstrCmd,const int nCmdLen,const bool wait);
                bool sendCmd(const void *pstrCmd,const int nCmdLen,const bool buffer = false);
                bool sendCmdNoPack(const void *pstrCmd,const int nCmdLen,const bool buffer = false);

                bool sync();
                void force_sync();

                int checkIOForRead();
                int checkIOForWrite();
                int recvToBuf_NoPoll();
                int recvToCmd_NoPoll(void *pstrCmd,const int nCmdLen);

                inline const char *getIP() const { return inet_ntoa(addr.sin_addr); }
                inline const DWORD getAddr() const { return addr.sin_addr.s_addr; }
                inline const WORD getPort() const { return ntohs(addr.sin_port); }
                inline const char *getLocalIP() const { return inet_ntoa(local_addr.sin_addr); }

                inline const WORD getLocalPort() const { return ntohs(local_addr.sin_port); }

                inline void setReadTimeout(const int msec) { rd_msec = msec; }
                inline void setWriteTimeout(const int msec) { wr_msec = msec; }
                inline void addEpoll(int kdpfd, __uint32_t events, void *ptr)
                {
                        struct epoll_event ev;
                        ev.events = events;
                        ev.data.ptr = ptr;
                        if( -1 == epoll_ctl(kdpfd, EPOLL_CTL_ADD, sock, &ev)) {
                                char _buf[100];
                                bzero(_buf, sizeof(_buf));
                                strerror_r(errno, _buf, sizeof(_buf));
                        }
                }
                inline void delEpoll(int kdpfd, __uint32_t events)
                {
                        struct epoll_event ev;
                        ev.events = events;
                        if( -1 == epoll_ctl(kdpfd, EPOLL_CTL_DEL, sock, &ev)) {
                                char _buf[100];
                                bzero(_buf, sizeof(_buf));
                                strerror_r(errno, _buf, sizeof(_buf));
                        }
                }
                inline void fillPollFD(struct pollfd &pfd,short events)
                {
                        pfd.fd = sock;
                        pfd.events = events;
                        pfd.revents = 0;
                }

                inline void setEncMethod(CEncrypt::encMethod m) { enc.setEncMethod(m); }
                inline void set_key_rc5(const BYTE *data,int nLen,int rounds) { enc.set_key_rc5(data,nLen,rounds); }
                inline void set_key_des(const_ZES_cblock *key) { enc.set_key_des(key); }
                inline DWORD snd_queue_size() { return _snd_queue.rd_size() + _enc_queue.rd_size(); }
                inline DWORD getBufferSize() const {return _rcv_queue.maxSize() + _snd_queue.maxSize();}
                inline int Sock() const { return sock;}

                void setSecruePolicy(const char *p){
                        secruePolicy =  p;
                }
                int recvToBuf_NoPoll_Flash();

                ctMutex m_Lock;

                inline void setLostPkg(const bool b)
                { 
                        lost_pkg_ = b; 
                }
                inline bool isLostPkg() const         
                { 
                        return lost_pkg_; 
                }

        private:
                int sock;			//socket fd
                struct sockaddr_in addr;	//socket address
                struct sockaddr_in local_addr;	//socket address
                int rd_msec;			//read timeout
                int wr_msec;			//write timeout

                t_BufferCmdQueue _rcv_queue;	//receive buffer queue
                DWORD _rcv_raw_size;		//receive buffer decode data size
                t_BufferCmdQueue _snd_queue;	//send buffer queue
                t_BufferCmdQueue _enc_queue;	//encode buffer	queue       
                DWORD _current_cmd;
                ctMutex mutex;			//lock

                ctTime last_check_time;

                DWORD bitmask;
                CEncrypt enc;			//encode method

                bool lost_pkg_;

                const char *secruePolicy;
                inline void clear_flag()                { bitmask = 0 ;}
                inline void set_flag(DWORD _f)          { bitmask |= _f; }
                inline bool isset_flag(DWORD _f) const  { return bitmask & _f; }
                inline void clear_flag(DWORD _f)        { bitmask &= ~_f; }
                inline void sdump(unsigned char* pData, char* function)
                {
                        BYTE *tp = pData;
                        tp += sizeof(BYTE);
                }

                inline bool need_enc() const            { return CEncrypt::ENCDEC_NONE!=enc.getEncMethod(); }

                inline DWORD packetMinSize() const      { return PH_LEN; }

                inline DWORD packetSize(const BYTE *in) const 
                {
#if 0 //test
                        Zebra::logger->debug("%s: *in=0X%08X", __PRETTY_FUNCTION__, *((DWORD *)in)); 
#endif
                        return PH_LEN + ((*((DWORD *)in)) & PACKET_MASK); 
                }

                inline int sendRawData(const void *pBuffer,const int nSize);
                inline bool sendRawDataIM(const void *pBuffer,const int nSize);
                inline int sendRawData_NoPoll(const void *pBuffer,const int nSize);
                inline bool setNonblock();
                inline int waitForRead();
                inline int waitForWrite();
                inline int recvToBuf();

                inline DWORD packetUnpack(BYTE *in,const DWORD nPacketLen,BYTE *out);
                template<typename buffer_type>
                        inline void packetAppend(const void *pData,const DWORD nLen,buffer_type &cmd_queue)
                        {
                                packetPackZip( pData,nLen,cmd_queue,PACKET_ZIP == (bitmask & PACKET_ZIP));
                                if (need_enc())
                                        packetPackEnc(cmd_queue,cmd_queue.rd_size());
                        }
                template<typename buffer_type>
                        inline void packetAppendNoEnc(const void *pData,const DWORD nLen,buffer_type &cmd_queue)
                        {
                                packetPackZip(pData,nLen,cmd_queue,PACKET_ZIP == (bitmask & PACKET_ZIP));

                        }
                template<typename buffer_type>
                        inline void packetPackEnc(buffer_type &cmd_queue,const DWORD current_cmd,DWORD offset = 0)
                        {
                                DWORD mod = (cmd_queue.rd_size() - offset) % 8;
                                if (0!=mod) {
                                        mod = 8 - mod;
                                        (*(DWORD *)(&(cmd_queue.rd_buf()[cmd_queue.rd_size() - current_cmd]))) += mod;
                                        cmd_queue.wr_reserve(mod);
                                        cmd_queue.wr_flip(mod);
                                }

                                enc.encdec(&cmd_queue.rd_buf()[offset],cmd_queue.rd_size() - offset,true);
                        }
        public:
                template<typename buffer_type>
                        static inline DWORD packetPackZip(const void *pData,const DWORD nLen,buffer_type &cmd_queue,const bool _compress = true)
                        {
                                DWORD nSize = nLen > MAX_DATASIZE ? MAX_DATASIZE : nLen;//nLen & PACKET_MASK;
                                DWORD nMask = 0;                                        //nLen & (~PACKET_MASK);
                                if (nSize > PACKET_ZIP_MIN				//too big
                                                && _compress				//need compress
                                                /*&& !(nMask & PACKET_ZIP)*/)           //too big && compressed 
                                {
                                        uLong nZipLen = unzip_size(nSize);
                                        cmd_queue.wr_reserve(nZipLen + PH_LEN);
                                        int retcode = compress(&(cmd_queue.wr_buf()[PH_LEN]),&nZipLen,(const Bytef *)pData,nSize);
                                        switch(retcode) {
                                                case Z_OK:
                                                        break;
                                                case Z_MEM_ERROR:
                                                        break;
                                                case Z_BUF_ERROR:
                                                        break;
                                        }
                                        nSize = nZipLen;
                                        nMask |= PACKET_ZIP;
                                }
                                else {
                                        cmd_queue.wr_reserve(nSize + PH_LEN);
                                        bcopy(pData,&(cmd_queue.wr_buf()[PH_LEN]),nSize);
                                }
                                (*(DWORD *)(cmd_queue.wr_buf())) = (nSize | nMask);
#ifdef _SOCKET_TEST_DEBUG
                                BYTE *tp = &(cmd_queue.wr_buf()[PH_LEN]);
                                tp += sizeof(BYTE);
                                BYTE cmd = *((BYTE*)&(cmd_queue.wr_buf()[PH_LEN]));
                                BYTE para = *tp;
                                Zebra::logger->debug("packetPackZip() cmd_queue, cmd:%u, para:%u, size:%u", cmd, para, nSize);
                                if(cmd == 4 
                                                && para == 9) {
                                        Cmd::Super::t_RetCheckUser ts;
                                        bcopy((BYTE*)&(cmd_queue.wr_buf()[PH_LEN]), &ts, sizeof(ts));
                                        Zebra::logger->debug("zoneid:%u,accid:%u", ts.zoneid, ts.accid);
                                }
#endif
                                cmd_queue.wr_flip(nSize + PH_LEN);
                                return nSize + PH_LEN;
                        }
};

#include "command.h"
class ctProcessor
{
        public:
                virtual bool msgParse(const Cmd::t_NullCmd *,const DWORD) = 0;
                virtual ~ctProcessor() {}
};

#endif //BASE_CTSOCKET_H_
